#include "ZGifImage.h"
#include "ZThread.h"
#include "ZFileUtils.h"


namespace ZuiLib
{

ZGifImage::ZGifImage()
:m_pGIF(NULL), m_imageData(NULL),m_curIndex(0)
{

}

ZGifImage::~ZGifImage()
{
	DGifCloseFile(m_pGIF);
	zfree(m_imageData);
}

struct ZGifImageUserData
{
	const unsigned char* pData;
	int  nDataMark;
	int  nDataLen;
};

static int DecodeCallBackProc(GifFileType* gif, GifByteType* bytes, int size)
{
    ZGifImageUserData* data=(ZGifImageUserData*)gif->UserData;
    for(int i=0; i<size && data->nDataMark<data->nDataLen ; i++,data->nDataMark++)
    {
        bytes[i] = data->pData[data->nDataMark];
    }
    return size;
}

bool ZGifImage::LoadFromMem(const uint8_t* data,int len)
{
	if (m_pGIF)
	{
		DGifCloseFile(m_pGIF);
		zfree(m_imageData);
		m_imageData = NULL;
	}

    ZGifImageUserData udata;
    udata.pData = data;
	udata.nDataLen=len;
    if(!udata.pData || 0 == len)
    {
        return false;
    }
    udata.nDataMark = 0;
    int error = 0;
    m_pGIF = DGifOpen(&udata,&DecodeCallBackProc,&error);
    if (NULL == m_pGIF || DGifSlurp(m_pGIF) != GIF_OK)
    {
        DGifCloseFile(m_pGIF);
        m_pGIF = NULL;
        return false;
    }
	m_fileName.Empty();
    m_width = m_pGIF->SWidth;
    m_height = m_pGIF->SHeight;
    m_imageData = (unsigned char*)zmalloc(sizeof(unsigned char) * m_width * m_height * 4);
	memset(m_imageData,0,sizeof(unsigned char) * m_width * m_height * 4);
	LoadAllFrames();
	return true;
}

bool ZGifImage::LoadFromFile(const char* file)
{
	if (m_pGIF)
	{
		DGifCloseFile(m_pGIF);
		zfree(m_imageData);
		m_imageData = NULL;
	}

#ifdef __ANDROID__
	ZFileDataPtr data=ZFileUtils::Instance()->GetFileData(file);
	if (!data)
		return false;
	return LoadFromMem((const uint8_t*)data->data(),data->size());
#else
	int error = 0;
	ZString path=ZFileUtils::GetPathFromName(file);
	m_pGIF = DGifOpenFileName(path,&error);
	if (NULL == m_pGIF || DGifSlurp(m_pGIF) != GIF_OK)
	{
		DGifCloseFile(m_pGIF);
		m_pGIF = NULL;
		return false;
	}

	m_fileName = file;
	ZAnimateImageMgr::Instance()->SetImage(file,this);
	m_width = m_pGIF->SWidth;
	m_height = m_pGIF->SHeight;
	m_imageData = (unsigned char*)zmalloc(sizeof(unsigned char) * m_width * m_height * 4);
	memset(m_imageData,0,sizeof(unsigned char) * m_width * m_height * 4);
	LoadAllFrames();
	return true;
#endif
}



void ZGifImage::copyLine(unsigned char* dst, const unsigned char* src, const ColorMapObject* cmap, int transparent, int width)
{
    for (; width > 0; width--, src++, dst+=4) {
        if (*src != transparent) {
            const GifColorType& col = cmap->Colors[*src];
            *dst     = col.Red;
            *(dst+1) = col.Green;
            *(dst+2) = col.Blue;
            *(dst+3) = 0xFF;
        }
    }
}



void ZGifImage::setGifImageWithIndex(unsigned int index)
{
	ZASSERT(m_pGIF->ImageCount>0);
	if ((int)index >= m_pGIF->ImageCount)
	{
		index=m_pGIF->ImageCount-1;
	}
    m_curIndex = index;
    
	//memset(m_imageData, 0, sizeof(unsigned char)* m_width * m_height * 4);

    const SavedImage* cur = &m_pGIF->SavedImages[index];
    
    m_durTime = getImageDuration(cur);

	GifColorType bgColor;
	if (m_pGIF->SColorMap != NULL) {
		bgColor = m_pGIF->SColorMap->Colors[m_pGIF->SBackGroundColor];
	}

    if (index == 0)
    {
        bool trans;
        int disposal;
        this->getTransparencyAndDisposalMethod(cur, &trans, &disposal);

		if (!trans && m_pGIF->SColorMap != NULL) 
		{
			unsigned char* pixel = m_imageData;
			for (int i = 0; i < m_width * m_height; i++)
			{
				pixel[0] = bgColor.Red;
				pixel[1] = bgColor.Green;
				pixel[2] = bgColor.Blue;
				pixel[3] = 0xff;
				pixel += 4;
			}
		}
    }
    else
    {
        const SavedImage* prev = &m_pGIF->SavedImages[index-1];
        
        bool curTrans;
        int curDisposal;
        this->getTransparencyAndDisposalMethod(prev, &curTrans, &curDisposal);
        bool nextTrans;
        int nextDisposal;
        this->getTransparencyAndDisposalMethod(cur, &nextTrans, &nextDisposal);

        if (nextTrans || !checkIfCover(cur, prev))
        {
            if (curDisposal == 2)
            {
                unsigned char* dst = &m_imageData[(prev->ImageDesc.Top * m_width + prev->ImageDesc.Left) * 4];
                GifWord copyWidth = prev->ImageDesc.Width;
                
                if (prev->ImageDesc.Left + copyWidth > m_width)
                {
                    copyWidth = m_width - prev->ImageDesc.Left;
                }
                
                GifWord copyHeight = prev->ImageDesc.Height;
                if (prev->ImageDesc.Top + copyHeight > m_height)
                {
                    copyHeight = m_height - prev->ImageDesc.Top;
                }
                
				for (int i=0;i< copyHeight;++i)
				{
					for(int wIndex = 0; wIndex < copyWidth; wIndex++, dst+=4)
					{
						*dst     = bgColor.Red;
						*(dst+1) = bgColor.Green;
						*(dst+2) = bgColor.Blue;
						*(dst+3) = 0xff;
					}
				}
            }
        }
    }
    

    {
        int transparent = -1;
        for (int i = 0; i < cur->ExtensionBlockCount; ++i)
        {
            ExtensionBlock* eb = cur->ExtensionBlocks + i;
            if (eb->Function == GRAPHICS_EXT_FUNC_CODE &&
                eb->ByteCount == 4)
            {
                bool has_transparency = ((eb->Bytes[0] & 1) == 1);
                if (has_transparency)
                {
                    transparent = (unsigned char)eb->Bytes[3];
                }
            }
        }
        

		ColorMapObject* sColorMap=m_pGIF->SColorMap;
		if (cur->ImageDesc.ColorMap)
		{
			sColorMap=cur->ImageDesc.ColorMap;
		}

        if (sColorMap && sColorMap->ColorCount == (1 << sColorMap->BitsPerPixel))
        {
            unsigned char* src = (unsigned char*)cur->RasterBits;
            unsigned char* dst = &m_imageData[(cur->ImageDesc.Top * m_width + cur->ImageDesc.Left) * 4];
            
            GifWord copyWidth = cur->ImageDesc.Width;
            if (cur->ImageDesc.Left + copyWidth > m_width)
            {
                copyWidth = m_width - cur->ImageDesc.Left;
            }
            
            GifWord copyHeight = cur->ImageDesc.Height;
            if (cur->ImageDesc.Top + copyHeight > m_height)
            {
                copyHeight = m_height - cur->ImageDesc.Top;
            }
            
            for (; copyHeight > 0; copyHeight--)
            {
                copyLine(dst, src, sColorMap, transparent, copyWidth);
				src += cur->ImageDesc.Width;
				dst += m_width*4;
            }
        }
    }
}


void ZGifImage::getTransparencyAndDisposalMethod(const SavedImage* frame, bool* trans, int* disposal)
{
    *trans = false;
    *disposal = 0;
    for (int i = 0; i < frame->ExtensionBlockCount; ++i)
    {
        ExtensionBlock* eb = frame->ExtensionBlocks + i;
        if (eb->Function == GRAPHICS_EXT_FUNC_CODE &&
            eb->ByteCount == 4)
        {
            *trans = ((eb->Bytes[0] & 1) == 1);
            *disposal = ((eb->Bytes[0] >> 2) & 7);
        }
    }
}

bool ZGifImage::checkIfCover(const SavedImage* target, const SavedImage* covered)
{
    if (target->ImageDesc.Left <= covered->ImageDesc.Left
        && covered->ImageDesc.Left + covered->ImageDesc.Width <=
        target->ImageDesc.Left + target->ImageDesc.Width
        && target->ImageDesc.Top <= covered->ImageDesc.Top
        && covered->ImageDesc.Top + covered->ImageDesc.Height <=
        target->ImageDesc.Top + target->ImageDesc.Height) {
        return true;
    }
    return false;
}

bool ZGifImage::checkIfWillBeCleared(const SavedImage* frame)
{
    for (int i = 0; i < frame->ExtensionBlockCount; ++i)
    {
        ExtensionBlock* eb = frame->ExtensionBlocks + i;
        if (eb->Function == GRAPHICS_EXT_FUNC_CODE &&
            eb->ByteCount == 4)
        {
            // check disposal method
            int disposal = ((eb->Bytes[0] >> 2) & 7);
            if (disposal == 2 || disposal == 3)
            {
                return true;
            }
        }
    }
    return false;
}

int ZGifImage::getImageDuration(const SavedImage* image)
{
    int duration = 0;
    for (int j = 0; j < image->ExtensionBlockCount; j++)
    {
        if (image->ExtensionBlocks[j].Function == GRAPHICS_EXT_FUNC_CODE)
        {
            int size = image->ExtensionBlocks[j].ByteCount;
            //assert(size >= 4);
            if(size < 4) break;
            const uint8_t* b = (const uint8_t*)image->ExtensionBlocks[j].Bytes;
            duration =  ((b[2] << 8) | b[1]) * 10;
            break;
        }
    }
    duration = duration <= 50 ? 50 : duration;
    return duration;
}






bool ZGifImage::IsGif(const char* fileName)
{
	FILE* file = fopen(fileName,"rb");
	bool res = IsGifFile(file);
	fclose(file);
	return res;
}


bool ZGifImage::IsGifFile(FILE* file)
{
	if(file == NULL)
	{
		return false;
	}
	long index = ftell(file);
	bool res = false;
	char buf[GIF_STAMP_LEN];
	int read = fread(buf,1,GIF_STAMP_LEN,file);
	if (read == GIF_STAMP_LEN) {
		if (memcmp(GIF_STAMP,   buf, GIF_STAMP_LEN) == 0 ||
			memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
			memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) {
				res =  true;
		}
	}
	fseek(file,index,SEEK_SET);
	return res;
}


void ZGifImage::LoadAllFrames()
{
	if (!m_pGIF)
		return;
	m_cacheFrames.Empty();

	for (int i = 0; i < m_pGIF->ImageCount; ++i)
	{
		setGifImageWithIndex(i);
		int size = sizeof(unsigned char) * m_width * m_height * 4;
		ZRefCountedPtr<ZAnimateFrame> frame =
            new ZRefCountedObject<ZAnimateFrame>(m_imageData,size,m_durTime);
		m_cacheFrames.Add(frame);
	}

	DGifCloseFile(m_pGIF);
	m_pGIF = NULL;
	zfree(m_imageData);
	m_imageData = NULL;
}



}//namespace
